package com.glw.sso.platform.service.impl;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import com.glw.sso.platform.domain.feign.WeatherRemoteApi;
import com.glw.sso.platform.domain.feign.response.WeatherInfo;
import com.glw.sso.platform.service.DemoService;
import com.google.common.collect.ImmutableMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class DemoServiceImpl implements DemoService, InitializingBean {

    @Autowired
    private WeatherRemoteApi weatherRemoteApi;

    private LoadingCache<String, Object> cache;

    @Override
    // @Cacheable(value = "short", key = "targetClass + methodName +#p0", sync = true)
    public WeatherInfo weatherQuery(String city) {
        city = StringUtils.isEmpty(city) ? "北京" : city;
        Map<String, Object> param = ImmutableMap.of(
                "city", city,
                "key", "63a62b916c62d7116b676ae84b03569a"
        );
        return weatherRemoteApi.weatherQuery(param).getResult();
    }

    @Override
    @Cacheable(value = "short", key = "targetClass + methodName +#p0", sync = true)
    public String cacheableTest(String aParam) {
        return String.join(":", LocalDateTime.now().toString(), aParam);
    }

    @Override
    public Object caffeineCache(String key) {
        return cache.get(key);
    }

    @Override
    public CacheStats caffeineCacheStats() {
        return cache.stats();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        /*
         * 并发情况下只会有一个线程去拿新数据,防止缓存穿透
         */
        cache = Caffeine.newBuilder()
                // 打开统计
                .recordStats()
                /*
                 * 当缓存超出这个容量的时候，会使用Window TinyLfu策略来删除缓存
                 */
                .maximumSize(100)
                // 当垃圾收集器需要释放内存时驱逐
                .softValues()
                // 当key和value都没有引用时驱逐缓存
                // .weakKeys().weakValues()
                /*
                 * 这个参数是 LoadingCache 和 AsyncLoadingCache 的才会有的。
                 * 在刷新的时候如果查询缓存元素，其旧值将仍被返回，直到该元素的刷新完毕后结束后才会返回刷新后的新值
                 */
                .refreshAfterWrite(1, TimeUnit.MINUTES)
                /*
                 * 与 refreshAfterWrite 不同，expireAfterWrite 不会返回旧值，每次过期都会去取新值，在这个过程中其他get会被阻塞。
                 * 如果两个参数同时设置并且过期时间相同时，expireAfterWrite 会覆盖 refreshAfterWrite
                 *
                 * 最佳实践是：expireAfterWrite 设置一个相对较长的过期时间，防止用户长时间不访问后再次访问时返回过期很久的数据。
                 */
                .expireAfterWrite(5, TimeUnit.MINUTES)
                .build(k -> {
                    log.info(Thread.currentThread().getName());
                    /*try {
                        log.info("sleeping...");
                        Thread.sleep(1000 * 30);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }*/
                    log.info("run...");
                    return String.join(":", k, LocalDateTime.now().toString());
                });
    }
}

